package com.yhb.channel.impl.BJCJ;

import com.yhb.channel.*;
import com.yhb.channel.dao.BJCJ.BJCJChannelBoundRelationshipMapper;
import com.yhb.channel.dao.BJCJ.BJCJChannelBoundRelationshipRecord;
import com.yhb.channel.dao.flow.ExecutionFlow;
import com.yhb.channel.exception.*;
import com.yhb.channel.impl.BJCJ.http.BJCJClient;
import com.yhb.channel.impl.BJCJ.http.Request.*;
import com.yhb.channel.impl.BJCJ.http.Request.base.Product;
import com.yhb.channel.impl.BJCJ.http.Response.*;
import com.yhb.channel.impl.BJCJ.http.Response.async.MerchantConsumeAsyncResponse;
import com.yhb.channel.model.*;
import com.yhb.channel.provider.*;
import com.yhb.channel.provider.event.EventType;
import com.yhb.channel.provider.event.eventImpls.BJCJ.PollingRemitResultEvent;
import com.yhb.channel.utils.ChannelDateUtil;
import com.yhb.channel.utils.ChannelJSONUtil;
import com.yhb.channel.utils.ChannelLogger;
import com.yhb.channel.utils.ChannelStringUtils;
import lombok.extern.log4j.Log4j;
import lombok.var;
import org.apache.commons.io.IOUtils;
import org.apache.http.util.TextUtils;
import org.jetbrains.annotations.NotNull;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 北京畅捷通道类
 */
@Log4j
public class BJCJChannel implements PayChannel {
    /* ------------配置------------ */
    private String baseUrl;

    /* ------------常量------------ */
    public static final String CHANNEL_BASE_NAME = "北京畅捷";
    public static final String CHANNEL_BASE_TYPE = ChannelFactory.BJCJ;

    /* ----每个子通道不一样的常量----- */
    public String subChannelCode;
    public String bjcjRedisBindcardSmsPrefix;
    public String bjcjRedisTryPayTokenKeyPrefix;
    public String channelFullName;
    public String channelFullType;

    /* ------------Base Provider Beans or utils------------ */
    private ChannelBaseService channelBaseService;
    private ChannelLogger logger;

    /* ------------BJCJ Provider Beans or utils------------ */
    private BJCJAsyncService asyncService;
    private BJCJClient bjcjClient;
    private BJCJChannelBoundRelationshipMapper db;

    // 在工厂函数里注入需要注入的东西
    public BJCJChannel(String baseUrl, String subChannelCode, ChannelBaseService channelBaseService, BJCJAsyncService asyncService, BJCJClient bjcjClient, BJCJChannelBoundRelationshipMapper db, ChannelLogger logger) {
        this.baseUrl = baseUrl;
        this.subChannelCode = subChannelCode;
        this.bjcjRedisBindcardSmsPrefix = "bjcj-" + subChannelCode + "-redis-bindcard-sms-prefix-";
        this.bjcjRedisTryPayTokenKeyPrefix = "bjcj-"  + subChannelCode + "-redis-try-pay-token-key-prefix-";
        this.channelBaseService = channelBaseService;
        this.asyncService = asyncService;
        this.bjcjClient = bjcjClient;
        this.db = db;
        this.logger = logger;
        this.channelFullName = CHANNEL_BASE_NAME + '-' + subChannelCode;
        this.channelFullType = CHANNEL_BASE_TYPE + '-' + subChannelCode;
        channelBaseService.eventBus.on(this::eventPayloadParser); // 注册事件到eventPayloadParser
    }

    /* ------------业务方法------------ */
    /**
     * 检查是否签约过
     * @param userId 用户
     * @return 是否签约过
     */
    private boolean checkIfRegistered(String userId) {
        String cardId = db.selectBillingCardIdByUserId(userId, subChannelCode);
        return !TextUtils.isEmpty(cardId);
    }

    /* ------------PayChannel方法------------ */
    @Override
    public void userSignUp(String userId, String province, String city, String area, @NotNull IBankCard card, String payRate) {
        // meta信息
        String reqFlowNo = UUID.randomUUID().toString().replaceAll("-", "");

        // 用户相关
        IUserInfo userInfo = channelBaseService.iUserInfoProvider.getUserInfo(userId);
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        String merchAddress = "广东省深圳市春南路2333号糖薰公寓4栋102号";
        String merName = userIdentify.getUserName();
        String idCardNo = userIdentify.getIdCardNum();

        // 卡相关
        String bankAccountNo = card.getNumber();
        String mobile = card.getPhoneNumber();
        String bankName = card.getBankName();
        String bankAccountType = "2";
        String bankSubName = ChannelStringUtils.stringOr(card.getSubBankName(), card.getBankName());
        BJCJBank bjcjBank = BJCJConstant.getBank(bankName);
        String bankCode = bjcjBank.getCode();
        String bankAbbr = bjcjBank.getAbbreviation();
        String bankChannelNo = "000000000000"; // 联行号
        BJCJRegion region = BJCJConstant.getRegion(province, city, area);
        String bankProvince = region.getProvinceCode();
        String bankCity = region.getCityCode();
        String txSingleFee = "200";
        Product[] products = {
                Product.builder()
                        .payRate(payRate)
                        .channelCode(subChannelCode)
                        .build(),
        };

        SignUpRequest signUpRequest = SignUpRequest.builder()
                .merName(merName)
                .merchAddress(merchAddress)
                .merAbbr(merName)
                .idCardNo(idCardNo)
                .bankAccountNo(bankAccountNo)
                .mobile(mobile)
                .bankAccountName(merName)
                .bankAccountType(bankAccountType)
                .bankName(bankName)
                .bankSubName(bankSubName)
                .bankCode(bankCode)
                .bankAbbr(bankAbbr)
                .bankChannelNo(bankChannelNo)
                .bankProvince(bankProvince)
                .bankCity(bankCity)
                .txSingleFee(txSingleFee)
                .productList(products)
                .build();
        signUpRequest.setReqFlowNo(reqFlowNo);
        signUpRequest.setMerId(userId);
        SignUpResponse response = bjcjClient.http(SignUpRequest.action, signUpRequest, SignUpResponse.class, subChannelCode);
        logger.info("Response:" + ChannelJSONUtil.toJson(response));

        if (response.isSuccess() && response.getData().isSuccess()) {
            BJCJChannelBoundRelationshipRecord record = BJCJChannelBoundRelationshipRecord.builder()
                    .agreementNo("")
                    .userId(userId)
                    .cardId(card.getId())
                    .bound(0) // 只注册未绑定
                    .isBillingCard(1)
                    .channelUserId(userId) // 和userId一致
                    .cardType(card.getCardType())
                    .build();
            if (db.getBillingCardRecordByUserId(userId, subChannelCode) != null) {
                // 有结算卡 更新结算卡信息
                db.updateByUserId(record, subChannelCode);
            } else {
                db.insert(record, subChannelCode);
            }
            logger.info("addMerInf success");
            return;
        }
        logger.fail(BJCJConstant.ERROR_DEFAULT_CODE, "入网失败:\n"
                + response.getMessage() + "\n" + response.getData().getMessage());
    }

    @Override
    public void updatePayRate(String userId, String payRate) {
        String modifyType = "1";
        UpdateMerInfRequest request = UpdateMerInfRequest.builder()
                .modifyType(modifyType)
                .payRate(payRate)
                .bankChannelNo("000000000000")
                .build();
        request.setMerId(userId);
        request.setChannelCode(subChannelCode);
        UpdateMerInfResponse response = bjcjClient.http(UpdateMerInfRequest.action, request,
                UpdateMerInfResponse.class, subChannelCode);
        if (response.isSuccess() && response.getData().isSuccess()) {
            logger.info("支付费率修改成功");
            return;
        }
        logger.fail(BJCJConstant.ERROR_DEFAULT_CODE,
                "支付费率修改失败:\n" + response.getMessage() + "\n" + response.getData().getMessage());
    }

    @Override
    public String bindCard(String userId, @NotNull IBankCard card, String clientIP) {
        String reqFlowNo = UUID.randomUUID().toString().replaceAll("-", "");

        // 身份证相关
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        String idCardNo = userIdentify.getIdCardNum();
        String userName = userIdentify.getUserName();

        // 结算卡相关
        String bankAccountNo = card.getNumber();
        String mobile = card.getPhoneNumber();
        String cardType = "0" + (card.getCardType() + 1); // 01储蓄 02信用
        String cvn2 = "";
        String expired = "";
        String callbackUrl = "";
        String bankName = card.getBankName();
        BJCJBank bank = BJCJConstant.getBank(bankName);
        String bankCode = bank.getAbbreviation(); // 畅捷通道方面表示 有别于签约入网 这里bankCode传英文
        String bankChannelNo = "000000000000";

        if (cardType.equals("02")) {
            cvn2 = card.getCvv2();
            String validation = card.getValidation(); // MMYY
            expired = validation.substring(2) + validation.substring(0, 2); // 通道要求YYMM
        }

        BindCardSmsRequest request = BindCardSmsRequest.builder()
                .idCardNo(idCardNo)
                .bankAccountNo(bankAccountNo)
                .mobile(mobile)
                .bankAccountName(userName)
                .cardType(cardType)
                .cvn2(cvn2)
                .expired(expired)
                .callbackUrl(callbackUrl)
                .bankCode(bankCode)
                .bankChannelNo(bankChannelNo)
                .clientIp(clientIP)
                .build();
        request.setReqFlowNo(reqFlowNo);
        request.setMerId(userId);
        request.setChannelCode(subChannelCode);
        BindCardSmsResponse response = bjcjClient.http(
                BindCardSmsRequest.action, request, BindCardSmsResponse.class, subChannelCode);
        if (response.isSuccess() && response.getData().isSuccess()) {
            if (response.getData().getBindStatus().equals("2")) {
                logger.fail(BJCJConstant.ERROR_DEFAULT_CODE, "绑卡失败： bindMsg:\n "
                        + response.getData().getBindMsg() + "\n ---message: \n" + response.getData().getMessage());
            }
            BJCJBindCardPayload payload = BJCJBindCardPayload.builder()
                    .userId(userId)
                    .card(card)
                    .clientIP(clientIP)
                    .reqFlowNo(reqFlowNo)
                    .channelCode(subChannelCode)
                    .build();
            String uuid = UUID.randomUUID().toString();
            channelBaseService.redis.boundValueOps(bjcjRedisBindcardSmsPrefix + uuid).set(
                    ChannelJSONUtil.toJson(payload), ChannelConstant.ONE_DAY, TimeUnit.MILLISECONDS);
            logger.info("bindcardSms success");
            return uuid;
        }
        // 重复绑卡
        if (BJCJConstant.DUPLICATED_BOUND_CARD.equals(response.getCode())) {
            logger.info("DUPLICATED_BOUND_CARD: " + card.getId());
            BJCJChannelBoundRelationshipRecord existsRecord = db.getRecordByCardId(card.getId(), subChannelCode);
            if (existsRecord == null) {
                BJCJChannelBoundRelationshipRecord record = BJCJChannelBoundRelationshipRecord.builder()
                        .cardId(card.getId())
                        .bound(1)
                        .isBillingCard(0)
                        .cardType(card.getCardType())
                        .userId(userId)
                        .channelUserId(userId)
                        .agreementNo("ok")
                        .build();
                db.insert(record, subChannelCode);
            } else {
                existsRecord.setAgreementNo("ok");
                existsRecord.setBound(1); // 标志绑过卡
                db.updateByCardId(existsRecord, subChannelCode);
            }
            throw new CardAlreadyBoundException(); // 让业务发短信
        }
        logger.fail(BJCJConstant.ERROR_DEFAULT_CODE,
                "绑卡失败:\n" + response.getMessage() + "\n"
                        + response.getData().getMessage() + "\n" + response.getData().getBindMsg());
        return null; // @unreachable
    }

    @Override
    public String tryBindCard(String userId, IBankCard card, String clientIP) {
        var location = channelBaseService.channelLocationService.getLocation();
        var province = location.getProvince();
        var city = location.getCity();
        var area = location.getArea();
        var payRate = "0.55";
        return tryBindCard(userId, card, clientIP, payRate, province, city, area);
    }

    @Override
    public String tryBindCard(String userId, @NotNull IBankCard card,
                              String clientIP, String payRate,
                              String province, String city, String area
    ) {
        boolean registered = this.checkIfRegistered(userId);
        int cardType = card.getCardType(); // 0 - 储蓄卡, 1 - 信用卡
        if (!registered) {
//            if (cardType == 1) {
//                logger.fail(SXYConstant.NOT_SIGNED, "该用户未签约过，请先签约");
//                return ""; // @unreachable
//            }
            this.userSignUp(userId, province, city, area, card, payRate);
        }
        return this.bindCard(userId, card, clientIP);
    }

    @Override
    public String bindCardConfirm(String smsCode, String userId, String smsUUID) {
        String valStr = channelBaseService.redis.boundValueOps(bjcjRedisBindcardSmsPrefix + smsUUID).get();
        if (valStr == null || valStr.isEmpty()) {
            logger.fail(BJCJConstant.SMS_EXPIRED, "短信已过期!");
        }
        BJCJBindCardPayload payload = ChannelJSONUtil.parseJson(valStr, BJCJBindCardPayload.class);
        IBankCard card = payload.getCard();
        String reqFlowNo = payload.getReqFlowNo();
        String clientIP = payload.getClientIP();
//        String channelCode = payload.getChannelCode();

        // 身份证相关
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        String idCardNo = userIdentify.getIdCardNum();
        String userName = userIdentify.getUserName();

        // 结算卡相关
        String bankAccountNo = card.getNumber();
        String mobile = card.getPhoneNumber();
        String cardType = "0" + (card.getCardType() + 1);
        String cvn2 = "";
        String expired = "";
        if (cardType.equals("02")) {
            cvn2 = card.getCvv2();
            expired = card.getValidation();
        }

        BindCardConfirmRequest request = BindCardConfirmRequest.builder()
                .idCardNo(idCardNo)
                .bankAccountNo(bankAccountNo)
                .mobile(mobile)
                .bankAccountName(userName)
                .smsCode(smsCode)
                .cardType(cardType)
                .cvn2(cvn2)
                .expired(expired)
                .clientIp(clientIP)
                .build();
        request.setReqFlowNo(reqFlowNo);
        request.setMerId(userId);
        request.setChannelCode(subChannelCode);
        BindCardConfirmResponse response = bjcjClient.http(BindCardConfirmRequest.action,
                request, BindCardConfirmResponse.class, subChannelCode);
        if (response.isSuccess() && response.getData().isSuccess()) {
            if (response.getData().getBindStatus().equals("2")) {
                logger.fail(BJCJConstant.ERROR_DEFAULT_CODE, "绑卡确认失败： bindMsg: \n"
                        + response.getData().getBindMsg() + "\n ---message: \n" + response.getData().getMessage());
                return null; // @unreachable
            }
            /*
              判断交易卡是不是和结算卡同一张 防止重复插入同个cardId
             */
            BJCJChannelBoundRelationshipRecord existsRecord = db.getRecordByCardId(card.getId(), subChannelCode);
            if (existsRecord != null) {
                existsRecord.setAgreementNo("ok");
                existsRecord.setBound(1); // 标志绑过卡
                db.updateByCardId(existsRecord, subChannelCode);
            } else {
                BJCJChannelBoundRelationshipRecord record = BJCJChannelBoundRelationshipRecord.builder()
                        .cardId(card.getId())
                        .bound(1)
                        .isBillingCard(0)
                        .cardType(card.getCardType())
                        .userId(userId)
                        .channelUserId(userId)
                        .agreementNo("ok")
                        .build();
                db.insert(record, subChannelCode);
            }
            return card.getId();
        }
        logger.fail(BJCJConstant.ERROR_DEFAULT_CODE,
                "绑卡确认失败:\n" + response.getMessage() + "\n" + response.getData().getMessage());
        return null; // @unreachable
    }

    @Override
    public String pay(String userId,
                      @NotNull IBankCard payCard, IBankCard remitCard,
                      @NotNull BigDecimal orderAmount, BigDecimal userFeeAmount,
                      String clientIP,
                      String province, String city, String area
    )
            throws PayChannelDeadException {
        if (orderAmount.compareTo(BigDecimal.valueOf(10)) < 0) {
            logger.fail(BJCJConstant.ERROR_DEFAULT_CODE, "交易金额过小");
        }

        String reqFlowNo = UUID.randomUUID().toString().replaceAll("-", "");
        String merId = userId;
        IUserInfo iUserInfo = channelBaseService.iUserInfoProvider.getUserInfo(userId);

        // 身份证相关
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        String idCardNo = userIdentify.getIdCardNum();
        String userName = userIdentify.getUserName();

        // 支付卡相关
        String bankAccountNo = payCard.getNumber();
        String mobile = payCard.getPhoneNumber();
        String cvn2 = "";
        String validation;
        String expired = "";
        String cardType = "0" + (payCard.getCardType() + 1); // 01储蓄 02信用
        if ("02".equals(cardType)) {
            cvn2 = payCard.getCvv2();
            validation = payCard.getValidation(); // MMYY
            expired = validation.substring(2) + validation.substring(0, 2); // 通道要求YYMM
        }

        var orderCreateTimestamp = String.valueOf(System.currentTimeMillis());
        String callbackUrl = baseUrl + "channel/callback/" + channelFullType + "/payCallback/" + orderCreateTimestamp;
        String bankName = payCard.getBankName();
        BJCJRegion region = BJCJConstant.getRegion(province, city, area);

        MerchantConsumeRequest request = MerchantConsumeRequest.builder()
                .idCardNo(idCardNo)
                .bankAccountNo(bankAccountNo)
                .bankName(bankName)
                .mobile(mobile)
                .bankAccountName(userName)
                .cardType(cardType)
                .cvn2(cvn2)
                .expired(expired)
                .provinceName(region.getProvinceCode())
                .cityName(region.getCityCode())
                .trxSourceIp(clientIP)
                .orderAmount(orderAmount.multiply(BigDecimal.valueOf(100L)).longValue() + "")
                .callbackUrl(callbackUrl)
                .mcc("")
                .build();
        request.setReqFlowNo(reqFlowNo);
        request.setMerId(merId);
        request.setChannelCode(subChannelCode);
        MerchantConsumeResponse response = bjcjClient.http(MerchantConsumeRequest.action,
                request, MerchantConsumeResponse.class, subChannelCode);
        if (response.isSuccess() && response.getData().isSuccess()) {
            ExecutionFlow executionFlow = ExecutionFlow.builder()
                    .userId(userId)
                    .status(ExecutionFlow.FlowStatus.INITIALIZED) // 在回调中改状态
                    .type(ExecutionFlow.Type.PAY)
                    .channelType(channelFullType)
                    .amount(orderAmount)
                    .extendInfo(ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                        put("request", ChannelJSONUtil.toJson(request));
                        put("response", ChannelJSONUtil.toJson(response));
                    }}))
                    .cardId(payCard.getId())
                    .requestId(reqFlowNo)
                    .commission(userFeeAmount)
                    .remitFee(BigDecimal.ZERO)
                    .orderId(reqFlowNo) // 对畅捷而言 就是订单号
                    .retryId("")
                    .vipType(iUserInfo.getStringVipType())
                    .createTime(new Date())
                    .build();
            String executionFlowId = channelBaseService.flowService.add(executionFlow);
            logger.info("pay success with flow id：" + executionFlowId);
            return executionFlowId;
        }
        // 失败了
        var failReason = response.getMessage() + ", " + response.getData().getMessage();
        ExecutionFlow failFlow = ExecutionFlow.builder()
                .userId(userId)
                .status(ExecutionFlow.FlowStatus.FAILED) // 在回调中改状态
                .type(ExecutionFlow.Type.PAY)
                .channelType(channelFullType)
                .amount(orderAmount)
                .extendInfo(ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                    put("request", ChannelJSONUtil.toJson(request));
                    put("response", ChannelJSONUtil.toJson(response));
                }}))
                .cardId(payCard.getId())
                .requestId(reqFlowNo)
                .commission(userFeeAmount)
                .remitFee(BigDecimal.ZERO)
                .orderId(reqFlowNo) // 对畅捷而言 就是订单号
                .retryId("")
                .vipType(iUserInfo.getStringVipType())
                .createTime(new Date())
                .failReason(failReason)
                .build();
        String failId = channelBaseService.flowService.add(failFlow);
        logger.info("pay fail with flow id：" + failId + ", and failReason is: " + failReason);
        // 超限 此通道不适用
        if (response.isSuccess() &&
                (BJCJConstant.THIS_MONTH_OUT_OF_LIMIT_AMOUNT_CODE.equals(response.getData().getCode()) ||
                        BJCJConstant.TODAY_OUT_OF_LIMIT_AMOUNT_CODE.equals(response.getData().getCode()))
        ) {
            throw PayChannelDeadException.builder()
                    .amount(orderAmount)
                    .payCard(payCard)
                    .remitCard(remitCard)
                    .clientIP(clientIP)
                    .userFeeAmount(userFeeAmount)
                    .channelName(channelFullName)
                    .build();
        }
        logger.fail(ChannelConstant.PAY_ERROR,
                "支付失败:" + failReason, null, failId);
        return null; // @unreachable
    }

    @Override
    public String tryPay(String userId,
                         IBankCard payCard, IBankCard remitCard,
                         BigDecimal orderAmount, BigDecimal userFeeAmount,
                         String clientIP, String payRate, String province, String city, String area)
                            throws TryingBindCardException, PayChannelDeadException {
        if (!this.checkIfBound(payCard, false)) {
            try {
                String bindCardUUID = this.tryBindCard(userId, payCard, clientIP, payRate, province, city, area);
                TryPayPayload payload = TryPayPayload.builder()
                        .payCard(payCard)
                        .remitCard(remitCard)
                        .bindCardUUID(bindCardUUID)
                        .orderAmount(orderAmount)
                        .userFeeAmount(userFeeAmount)
                        .userId(userId)
                        .clientIP(clientIP)
                        .payRate(payRate)
                        .province(province)
                        .city(city)
                        .area(area)
                        .payExecutionFlowId(null)
                        .build();
                String msg = CHANNEL_BASE_NAME + " " + payCard.getBankName() + ChannelStringUtils.getMaskString(
                        payCard.getNumber(), 0, 3, 4) + "验证码";
                throw TryingBindCardException.builder()
                        .msg(msg)
                        .phoneNumber(payCard.getPhoneNumber())
                        .channelName(channelFullName)
                        .channelType(channelFullType)
                        .tryPayPayloadJson(ChannelJSONUtil.toJson(payload))
                        .build();
            } catch (CardAlreadyBoundException passThrough) {
                throw passThrough;
            } catch (PayChannelException e) {
                logger.errorInfo(e.getMessage(), e);
                throw PayChannelDeadException.builder()
                        .userFeeAmount(userFeeAmount)
                        .payRate(payRate)
                        .clientIP(clientIP)
                        .channelName(channelFullName)
                        .remitCard(remitCard)
                        .payCard(payCard)
                        .amount(orderAmount)
                        .build();
            }
        }
        try {
            this.updatePayRate(userId, payRate);
            return this.pay(
                    userId,
                    payCard, remitCard,
                    orderAmount, userFeeAmount,
                    clientIP,
                    province, city, area
            );
        } catch (PayChannelDeadException e) {
            logger.errorInfo(e.getMessage(), e);
            e.setPayRate(payRate); // 补全pay方法里没能写入的信息
            throw e; // 抛出给dispatcher处理
        }
    }

    @Override
    public String verifyPay(String userId, String smsCode,
                            @NotNull TryPayPayload payload) throws TryingBindCardException, PayChannelDeadException {
        String uuid = payload.getBindCardUUID();
        String cardId = this.bindCardConfirm(smsCode, userId, uuid); // 绑卡成功
        // 接下来 重放消费
        return this.tryPay(
                userId,
                payload.getPayCard(), payload.getRemitCard(),
                payload.getOrderAmount(), payload.getUserFeeAmount(),
                payload.getClientIP(), payload.getPayRate(),
                payload.getProvince(), payload.getCity(), payload.getArea()
        );
    }

    @Override
    public String remit(
            String userId, @NotNull IBankCard card, @NotNull IBankCard payCard,
            @NotNull BigDecimal amount, BigDecimal userFeeAmount,
            boolean active, TryBindCardExtendInfo tryBindCardExtendInfo
    ) throws RemitCardUnboundException {
        if (active) {
            // 对于手动提现的情况 检查代付卡绑定情况
            if (!checkIfBound(card, true)) {
                String bindCardUUID = this.tryBindCard(userId, card,
                        tryBindCardExtendInfo.getClientIP(),
                        tryBindCardExtendInfo.getPayRate(),
                        tryBindCardExtendInfo.getProvince(),
                        tryBindCardExtendInfo.getCity(),
                        tryBindCardExtendInfo.getArea()
                );
                throw RemitCardUnboundException.builder()
                        .channelName(channelFullName)
                        .channelType(channelFullType)
                        .userFeeAmount(userFeeAmount)
                        .amount(amount)
                        .msg("主动发起提现，代付卡绑定中")
                        .smsToken(bindCardUUID)
                        .userId(userId)
                        .remitCard(card)
                        .build();
            }
        }
        String reqFlowNo = UUID.randomUUID().toString().replaceAll("-", "");

        IUserInfo iUserInfo = channelBaseService.iUserInfoProvider.getUserInfo(userId);

        // 身份证相关
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        String idCardNo = userIdentify.getIdCardNum();
        String userName = userIdentify.getUserName();

        // 代付收款卡相关
        String bankAccountNo = card.getNumber();
        String mobile = card.getPhoneNumber();
        String cardType = "01"; // 代付收款卡一定是储蓄卡
        String bankName = card.getBankName();
        String bankSubName = card.getSubBankName();

        MerchantWithdrawRequest request = MerchantWithdrawRequest.builder()
                .amount(amount
                        .multiply(BigDecimal.valueOf(100L))
                        .add(userFeeAmount.multiply(BigDecimal.valueOf(100L))) // 代付加两块手续费
                        .longValue()
                        + "")
                .bankAccountNo(bankAccountNo)
                .idCardNo(idCardNo)
                .bankName(bankName)
                .mobile(mobile)
                .cardType(cardType)
                .bankAccountName(userName)
                .bankSubName(bankSubName)
                .bankChannelNo("000000000000")
                .walletType("")
                .build();
        request.setReqFlowNo(reqFlowNo);
        request.setMerId(userId);
        request.setChannelCode(subChannelCode);

        MerchantWithdrawResponse response = bjcjClient.http(MerchantWithdrawRequest.action,
                request, MerchantWithdrawResponse.class, subChannelCode);
        if (response.isSuccess() && response.getData().isSuccess()) {
            ExecutionFlow executionFlow = ExecutionFlow.builder( )
                    .userId(userId)
                    .type(active ? ExecutionFlow.Type.ACTIVE_REMIT : ExecutionFlow.Type.REMIT)
                    .status(ExecutionFlow.FlowStatus.INITIALIZED)
                    .channelType(channelFullType)
                    .amount(amount)
                    .extendInfo(ChannelJSONUtil.toJson(response))
                    .cardId(card.getId())
                    .requestId(reqFlowNo)
                    .commission(BigDecimal.ZERO)
                    .remitFee(userFeeAmount)
                    .orderId(reqFlowNo) // 对畅捷就是订单号
                    .retryId("")
                    .vipType(iUserInfo.getStringVipType())
                    .createTime(new Date())
                    .build();
            String executionFlowId = channelBaseService.flowService.add(executionFlow);
            logger.info("remit订单提交成功 流水id：" + executionFlowId + " 以下开始轮询订单状态: ");
            asyncService.pollingUpRemitResult(userId, reqFlowNo, subChannelCode, executionFlow);
            return executionFlowId;
        }
        // 如果提交不成功 直接标失败
        var failReason = response.getMessage() + ", " + response.getData().getMessage();
        ExecutionFlow executionFlow = ExecutionFlow.builder( )
                .userId(userId)
                .type(active ? ExecutionFlow.Type.ACTIVE_REMIT : ExecutionFlow.Type.REMIT)
                .status(ExecutionFlow.FlowStatus.FAILED)
                .channelType(channelFullType)
                .amount(amount)
                .extendInfo(ChannelJSONUtil.toJson(response))
                .cardId(card.getId())
                .requestId(reqFlowNo)
                .commission(BigDecimal.ZERO)
                .remitFee(userFeeAmount)
                .orderId(reqFlowNo) // 对畅捷就是订单号
                .retryId("")
                .vipType(iUserInfo.getStringVipType())
                .createTime(new Date())
                .failReason(failReason)
                .build();
        channelBaseService.iNotifyProvider.orderStatusNotify(executionFlow,
                INotifyProvider.OrderStatus.FAILED); // 通知业务失败
        return channelBaseService.flowService.add(executionFlow);
    }

    @Override
    public String retryRemit(String flowId, Date date) {
//        ExecutionFlow executionFlow = channelBaseService.flowService.getFlowPrecisely(flowId, date);
//        if (executionFlow == null ||
//                (
//                        !ExecutionFlow.Type.REMIT.equals(executionFlow.getType())
//                        && !ExecutionFlow.Type.ACTIVE_REMIT.equals(executionFlow.getType())
//                )
//        ) {
//            logger.fail(BJCJConstant.ERROR_PARAMETER_WRONG, "该笔流水不存在或者不是代付订单");
//        }
//        assert executionFlow != null;
//        if (!channelFullType.equals(executionFlow.getChannelType())) {
//            logger.fail(BJCJConstant.ERROR_PARAMETER_WRONG, "该笔流水不是畅捷" + subChannelCode + "通道的订单");
//        }
//        if (ExecutionFlow.FlowStatus.RETRIED.equals(executionFlow.getStatus())) {
//            logger.fail(BJCJConstant.ERROR_PARAMETER_WRONG, "该订单已经被重试");
//        }
//        if (!ExecutionFlow.FlowStatus.FAILED.equals(executionFlow.getStatus())) {
//            logger.fail(BJCJConstant.ERROR_PARAMETER_WRONG, "该笔流水不是失败单，无需重试");
//        }
//        BigDecimal amount = executionFlow.getAmount();
//        BigDecimal remitFee = executionFlow.getRemitFee();
//        String cardId = executionFlow.getCardId();
//        String userId = executionFlow.getUserId();
//        IBankCard card = channelBaseService.iUserBankCardProvider.getBankCard(cardId);
//        String newId = null;
//        try {
//            newId = remit(userId, card, amount, remitFee, false, null);
//        } catch (RemitCardUnboundException ignored) {
//            // remit调用参数active为false时 不会抛这个RemitCardUnboundException
//        }
//        // 更新旧订单状态为ExecutionFlow.FlowStatus.RETRIED
//        logger.info("流水" + flowId + "重试成功，新id为" + newId);
//        executionFlow.setStatus(ExecutionFlow.FlowStatus.RETRIED);
//        executionFlow.setRetryId(newId);
//        channelBaseService.flowService.updatePrecisely(executionFlow, date);
//        return newId;
        return "";
    }

    @Override
    public boolean checkIfBound(@NotNull IBankCard card, boolean isRemitCard) {
        // 支付卡才需要绑定
        if (isRemitCard) {
            return true;
        }
        BJCJChannelBoundRelationshipRecord record = db.getRecordByCardId(card.getId(), subChannelCode);
        return !(record == null || record.getBound() == 0);
    }

    @Override
    public void handleCallback(String callBackAction, HttpServletRequest request, HttpServletResponse response, String orderCreateTimestamp, String info) {
        // 对畅捷而言 支付有回调 代付无回调
        String payFail = "3";
        String paySuccess = "2";
        var createTime = new Date(Long.parseLong(orderCreateTimestamp));

        if (BJCJConstant.CALLBACK_PAY.equals(callBackAction)) {
            logger.info("收到支付回调: orderCreateTimestamp => " + orderCreateTimestamp + ", info => " + info);
            try {
                String json = IOUtils.toString(request.getReader());
                logger.info("callback body json: " + json);
                MerchantConsumeAsyncResponse merchantConsumeAsyncResponse
                        = ChannelJSONUtil.parseJson(json, MerchantConsumeAsyncResponse.class);
                String requestId = merchantConsumeAsyncResponse.getOrderNo(); // 订单号就是requestId
                if (payFail.equals(merchantConsumeAsyncResponse.getOrderStatus())) {
                    // 支付失败了
                    logger.info("requestId: " + requestId + "支付失败："
                            + merchantConsumeAsyncResponse.getOrderMsg());

                    // 查询原流水
                    ExecutionFlow executionFlow = ExecutionFlow.builder()
                            .requestId(requestId)
                            .createTime(createTime)
                            .build();
                    ExecutionFlow targetFlow = channelBaseService.flowService.getByRequestIdAndCreateTime(executionFlow);

                    // 查询失败原因
                    var reason = "";
                    var consumeQueryRequest = ConsumeQueryRequest.builder().build();
                    consumeQueryRequest.setChannelCode(subChannelCode);
                    consumeQueryRequest.setMerId(targetFlow.getUserId());
                    consumeQueryRequest.setReqFlowNo(targetFlow.getOrderId());
                    var consumeQueryResponse = bjcjClient.http(ConsumeQueryRequest.action,
                            consumeQueryRequest,
                            ConsumeQueryResponse.class,
                            subChannelCode
                    );
                    if (consumeQueryResponse.isSuccess()) {
                        reason = consumeQueryResponse.getData().getOrderMsg();
                    }

                    // 更新extendInfo
                    var requestCtx = targetFlow.getExtendInfo(); // 原本只有流水信息
                    var finalReason = reason; // 内部类访问 需要final
                    var newInfo = ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                        put("reason", finalReason);
                        put("requestCtx", requestCtx);
                    }});

                    // 对失败状态和extendInfo落库 并通知业务
                    ExecutionFlow failFlow = channelBaseService.flowService.fail(executionFlow, newInfo);
                    if (failFlow == null) {
                        logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单");
                        return;
                    }
                    channelBaseService.iNotifyProvider.orderStatusNotify(failFlow, INotifyProvider.OrderStatus.FAILED);

                } else if (paySuccess.equals(merchantConsumeAsyncResponse.getOrderStatus())) {
                    logger.info("requestId: " + requestId + "支付成功：" + merchantConsumeAsyncResponse.getOrderMsg());
                    ExecutionFlow executionFlow = ExecutionFlow.builder()
                            .requestId(requestId)
                            .createTime(createTime)
                            .build();
                    // 根据requestId和createTime落库 并通知业务
                    ExecutionFlow successFlow = channelBaseService.flowService.success(executionFlow);
                    if (successFlow == null) {
                        logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单");
                        return;
                    }
                    channelBaseService.iNotifyProvider.orderStatusNotify(successFlow, INotifyProvider.OrderStatus.SUCCEED);
                }
                response.getWriter().write(ChannelConstant.SUCCESS);
            } catch (IOException e) {
                logger.info("pay 回调异常：" + e.toString());
            }
        }
    }

    @Override
    public void tick() {
        // 查出一周内所有本通道pending态的订单 emit出去POLLING_ORDER_STATUS_EVENT事件
        ArrayList<String> pastDaysList = ChannelDateUtil.getPastDates(7, "yyyyMMdd");
        ArrayList<ExecutionFlow> allFlows = new ArrayList<>();
        for (String pastDate: pastDaysList) {
            if (!channelBaseService.flowService.checkTableExistsWithDate(pastDate)) {
                continue;
            }
            List<ExecutionFlow> flows = channelBaseService
                    .flowService.getPendingRemitFlowsByChannelType(channelFullType, pastDate);
            allFlows.addAll(flows);
        }
        allFlows.forEach(executionFlow -> {
            BJCJRemitResultPollingPayload payload = BJCJRemitResultPollingPayload.builder()
                    .remitFlow(executionFlow)
                    .reqFlowNo(executionFlow.getOrderId())
                    .userId(executionFlow.getUserId())
                    .build();
            PollingRemitResultEvent event = new PollingRemitResultEvent(payload);
            channelBaseService.eventBus.emit(event.toJson());
        });
    }

    @Override
    public void eventHandler(@NotNull EventType type, String payload) {
        logger.info("收到 " + type.name() + " 事件");
        switch (type) {
            case POLLING_ORDER_STATUS_EVENT:
                BJCJRemitResultPollingPayload bjcjRemitResultPollingPayload = ChannelJSONUtil.parseJson(payload,
                        BJCJRemitResultPollingPayload.class);
                this.handlePollingOrder(bjcjRemitResultPollingPayload);
                break;
            default:
                break;
        }
    }

    /**
     * 轮询订单信息，写入数据库
     * @param payload 事件payload
     */
    void handlePollingOrder(@NotNull BJCJRemitResultPollingPayload payload) {
        String reqFlowNo = payload.getReqFlowNo();
        String userId = payload.getUserId();
        ExecutionFlow remitFlow = payload.getRemitFlow();

        // 去数据库拿flow 跳过非pending态的flow
        ExecutionFlow targetFlow = channelBaseService.flowService.getByRequestIdAndCreateTime(remitFlow);
        if (!ExecutionFlow.FlowStatus.INITIALIZED.equals(targetFlow.getStatus())) {
            return;
        }

        String fullType = remitFlow.getChannelType(); // BJCJ-前缀的通道名
        String subType = fullType.substring(5);

        // mq发消息会发给所有通道 这里需做排重 不是自己通道的remitFlow不管
        if (!subChannelCode.equals(subType)) {
            return;
        }

        // 调用查询接口
        WithdrawQueryRequest withdrawQueryRequest = WithdrawQueryRequest.builder()
                .build();
        withdrawQueryRequest.setReqFlowNo(reqFlowNo);
        withdrawQueryRequest.setMerId(userId);
        withdrawQueryRequest.setChannelCode(subType);
        WithdrawQueryResponse withdrawQueryResponse = bjcjClient.http(WithdrawQueryRequest.action
                , withdrawQueryRequest, WithdrawQueryResponse.class, subChannelCode);

        // 检查查询结果
        if (!withdrawQueryResponse.isSuccess() || !withdrawQueryResponse.getData().isSuccess()) {
            try {
                // 这种情况一般remit也是失败了
                // 而且不管失败不失败 这里都要标失败 防止OrderStatus保持INITIALIZED
                // 导致下次还是查这个单 依旧查询失败
                channelBaseService.flowService.fail(remitFlow);
                channelBaseService.iNotifyProvider.orderStatusNotify(remitFlow,
                        INotifyProvider.OrderStatus.FAILED); // 通知业务失败

                logger.fail(BJCJConstant.INNER_ERROR, subChannelCode + "轮询remit结果失败：\n"
                        + withdrawQueryResponse.getMessage() + "\n" + withdrawQueryResponse.getData().getMessage());
            } catch (Throwable ignored) {}
        } else if (!withdrawQueryResponse.orderStatusIsPending()) { // 结果不是pending
            if (withdrawQueryResponse.orderStatusIsSucceed()) { // 结果ok
                logger.info("reqFlowNo: " + reqFlowNo + " success");
                channelBaseService.flowService.success(remitFlow);
                channelBaseService.iNotifyProvider.orderStatusNotify(remitFlow,
                        INotifyProvider.OrderStatus.SUCCEED); // 通知业务成功
            } else if(withdrawQueryResponse.orderStatusIsFailed()) { // 结果fail
                logger.info("reqFlowNo: " + reqFlowNo + " fail");
                channelBaseService.flowService.fail(remitFlow);
                channelBaseService.iNotifyProvider.orderStatusNotify(remitFlow,
                        INotifyProvider.OrderStatus.FAILED); // 通知业务失败
            } else {
                try {
                    logger.fail(BJCJConstant.INNER_ERROR, "通道返回数据异常: "
                            + ChannelJSONUtil.toJson(withdrawQueryResponse));
                } catch (Throwable ignored) {}
            }
        }
    }

    @Override
    public ArrayList<ChannelBalance> getBalance(String userId) {
        ArrayList<ChannelBalance> list = new ArrayList<>();
        boolean registered = this.checkIfRegistered(userId);
        if (!registered) {
            // 未签约
            logger.info("未签约通道，余额-1");
            list.add(ChannelBalance.builder()
                    .channelName(channelFullName)
                    .channelType(channelFullType)
                    .balance(BigDecimal.valueOf(-1))
                    .build());
            return list;
        }
        WalletQueryRequest request = WalletQueryRequest.builder().build();
        String reqFlowNo = UUID.randomUUID().toString().replaceAll("-", "");
        request.setReqFlowNo(reqFlowNo);
        request.setMerId(userId);
        request.setChannelCode(subChannelCode);
        WalletQueryResponse response = bjcjClient.http(WalletQueryRequest.action,
                request, WalletQueryResponse.class, subChannelCode);
        if (response.isSuccess() && response.getData().isSuccess()) {
            String balance = response.getData().getBalance();
            list.add(ChannelBalance.builder()
                    .channelName(channelFullName)
                    .channelType(channelFullType)
                    .balance(
                            BigDecimal.valueOf(Integer.parseInt(balance))
                                    .divide(BigDecimal.valueOf(100)))
                    .build());
            return list;
        }
        list.add(
                ChannelBalance.builder()
                .channelName(channelFullName)
                .channelType(channelFullType)
                .balance(BigDecimal.valueOf(-1))
                .build()
        );
        logger.info("查询余额失败:\n" + response.getMessage() + "\n" + response.getData().getMessage());
        return list;

    }
}
